Loading the Dataset¶

In [2]:
import os
from PIL import Image
import matplotlib.pyplot as plt

# Define dataset paths (Stored in separate variables)
anemic_original =r"/kaggle/input/anerbc-dataset/AneRBC_dataset/AneRBC-II/Anemic_individuals/Original_images" 
anemic_binary =r"/kaggle/input/anerbc-dataset/AneRBC_dataset/AneRBC-II/Anemic_individuals/Binary_segmented"
anemic_rgb =r"/kaggle/input/anerbc-dataset/AneRBC_dataset/AneRBC-II/Anemic_individuals/RGB_segmented"


healthy_original =r"/kaggle/input/anerbc-dataset/AneRBC_dataset/AneRBC-II/Healthy_individuals/Original_images" 
healthy_binary =r"/kaggle/input/anerbc-dataset/AneRBC_dataset/AneRBC-II/Healthy_individuals/Binary_segmented"
healthy_rgb = r"/kaggle/input/anerbc-dataset/AneRBC_dataset/AneRBC-II/Healthy_individuals/RGB_segmented" 


# Store in a dictionary for structured access
directory = {
    "Anemic Original": anemic_original,
    "Anemic Binary": anemic_binary,
    "Anemic RGB": anemic_rgb,
    "Healthy Original": healthy_original,
    "Healthy Binary": healthy_binary,
    "Healthy RGB": healthy_rgb
}

# Display images
fig, axes = plt.subplots(2, 3, figsize=(15, 10))  # 2 rows, 3 columns for 6 images

for ax, (label, path) in zip(axes.flatten(), directory.items()):
    sample_image_name = os.listdir(path)[110]  # Selecting the 110th image
    image_path = os.path.join(path, sample_image_name)
    img = Image.open(image_path)

    ax.imshow(img)  # Display image
    ax.set_title(f"{label}")  # Dataset category as title
    ax.axis('off')  # Hide axes

plt.tight_layout()
plt.show()
No description has been provided for this image
In [3]:
import torch
print(torch.cuda.is_available(), torch.cuda.get_device_name(0))
True Tesla T4

U-Net for Anemic Binary RBC¶

In [4]:
import tensorflow as tf
from tensorflow.keras import layers, models

# ✅ UNet Encoder Block
def conv_block(input_tensor, num_filters):
    x = layers.Conv2D(num_filters, 3, padding="same")(input_tensor)
    x = layers.BatchNormalization()(x)
    x = layers.ReLU()(x)
    x = layers.Conv2D(num_filters, 3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.ReLU()(x)
    return x

# ✅ UNet Decoder Block
def decoder_block(input_tensor, skip_tensor, num_filters):
    x = layers.UpSampling2D((2, 2))(input_tensor)
    x = layers.Concatenate()([x, skip_tensor])
    x = conv_block(x, num_filters)
    return x

# ✅ Full UNet Model
def build_unet(input_shape=(224, 224, 1)):
    inputs = layers.Input(shape=input_shape)

    # Encoder
    c1 = conv_block(inputs, 64)
    p1 = layers.MaxPooling2D((2, 2))(c1)

    c2 = conv_block(p1, 128)
    p2 = layers.MaxPooling2D((2, 2))(c2)

    c3 = conv_block(p2, 256)
    p3 = layers.MaxPooling2D((2, 2))(c3)

    c4 = conv_block(p3, 512)
    p4 = layers.MaxPooling2D((2, 2))(c4)

    # Bottleneck
    bn = conv_block(p4, 1024)

    # Decoder
    d1 = decoder_block(bn, c4, 512)
    d2 = decoder_block(d1, c3, 256)
    d3 = decoder_block(d2, c2, 128)
    d4 = decoder_block(d3, c1, 64)

    # Output layer: 1-channel sigmoid for binary mask
    outputs = layers.Conv2D(1, 1, activation="sigmoid")(d4)

    model = models.Model(inputs, outputs, name="UNet")
    return model

# ✅ Instantiate the model
unet_model = build_unet()
unet_model.summary()
Model: "UNet"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Layer (type)              ┃ Output Shape           ┃        Param # ┃ Connected to           ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩
│ input_layer (InputLayer)  │ (None, 224, 224, 1)    │              0 │ -                      │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d (Conv2D)           │ (None, 224, 224, 64)   │            640 │ input_layer[0][0]      │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization       │ (None, 224, 224, 64)   │            256 │ conv2d[0][0]           │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu (ReLU)              │ (None, 224, 224, 64)   │              0 │ batch_normalization[0… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_1 (Conv2D)         │ (None, 224, 224, 64)   │         36,928 │ re_lu[0][0]            │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_1     │ (None, 224, 224, 64)   │            256 │ conv2d_1[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_1 (ReLU)            │ (None, 224, 224, 64)   │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ max_pooling2d             │ (None, 112, 112, 64)   │              0 │ re_lu_1[0][0]          │
│ (MaxPooling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_2 (Conv2D)         │ (None, 112, 112, 128)  │         73,856 │ max_pooling2d[0][0]    │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_2     │ (None, 112, 112, 128)  │            512 │ conv2d_2[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_2 (ReLU)            │ (None, 112, 112, 128)  │              0 │ batch_normalization_2… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_3 (Conv2D)         │ (None, 112, 112, 128)  │        147,584 │ re_lu_2[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_3     │ (None, 112, 112, 128)  │            512 │ conv2d_3[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_3 (ReLU)            │ (None, 112, 112, 128)  │              0 │ batch_normalization_3… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ max_pooling2d_1           │ (None, 56, 56, 128)    │              0 │ re_lu_3[0][0]          │
│ (MaxPooling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_4 (Conv2D)         │ (None, 56, 56, 256)    │        295,168 │ max_pooling2d_1[0][0]  │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_4     │ (None, 56, 56, 256)    │          1,024 │ conv2d_4[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_4 (ReLU)            │ (None, 56, 56, 256)    │              0 │ batch_normalization_4… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_5 (Conv2D)         │ (None, 56, 56, 256)    │        590,080 │ re_lu_4[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_5     │ (None, 56, 56, 256)    │          1,024 │ conv2d_5[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_5 (ReLU)            │ (None, 56, 56, 256)    │              0 │ batch_normalization_5… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ max_pooling2d_2           │ (None, 28, 28, 256)    │              0 │ re_lu_5[0][0]          │
│ (MaxPooling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_6 (Conv2D)         │ (None, 28, 28, 512)    │      1,180,160 │ max_pooling2d_2[0][0]  │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_6     │ (None, 28, 28, 512)    │          2,048 │ conv2d_6[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_6 (ReLU)            │ (None, 28, 28, 512)    │              0 │ batch_normalization_6… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_7 (Conv2D)         │ (None, 28, 28, 512)    │      2,359,808 │ re_lu_6[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_7     │ (None, 28, 28, 512)    │          2,048 │ conv2d_7[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_7 (ReLU)            │ (None, 28, 28, 512)    │              0 │ batch_normalization_7… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ max_pooling2d_3           │ (None, 14, 14, 512)    │              0 │ re_lu_7[0][0]          │
│ (MaxPooling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_8 (Conv2D)         │ (None, 14, 14, 1024)   │      4,719,616 │ max_pooling2d_3[0][0]  │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_8     │ (None, 14, 14, 1024)   │          4,096 │ conv2d_8[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_8 (ReLU)            │ (None, 14, 14, 1024)   │              0 │ batch_normalization_8… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_9 (Conv2D)         │ (None, 14, 14, 1024)   │      9,438,208 │ re_lu_8[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_9     │ (None, 14, 14, 1024)   │          4,096 │ conv2d_9[0][0]         │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_9 (ReLU)            │ (None, 14, 14, 1024)   │              0 │ batch_normalization_9… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ up_sampling2d             │ (None, 28, 28, 1024)   │              0 │ re_lu_9[0][0]          │
│ (UpSampling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ concatenate (Concatenate) │ (None, 28, 28, 1536)   │              0 │ up_sampling2d[0][0],   │
│                           │                        │                │ re_lu_7[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_10 (Conv2D)        │ (None, 28, 28, 512)    │      7,078,400 │ concatenate[0][0]      │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_10    │ (None, 28, 28, 512)    │          2,048 │ conv2d_10[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_10 (ReLU)           │ (None, 28, 28, 512)    │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_11 (Conv2D)        │ (None, 28, 28, 512)    │      2,359,808 │ re_lu_10[0][0]         │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_11    │ (None, 28, 28, 512)    │          2,048 │ conv2d_11[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_11 (ReLU)           │ (None, 28, 28, 512)    │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ up_sampling2d_1           │ (None, 56, 56, 512)    │              0 │ re_lu_11[0][0]         │
│ (UpSampling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ concatenate_1             │ (None, 56, 56, 768)    │              0 │ up_sampling2d_1[0][0], │
│ (Concatenate)             │                        │                │ re_lu_5[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_12 (Conv2D)        │ (None, 56, 56, 256)    │      1,769,728 │ concatenate_1[0][0]    │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_12    │ (None, 56, 56, 256)    │          1,024 │ conv2d_12[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_12 (ReLU)           │ (None, 56, 56, 256)    │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_13 (Conv2D)        │ (None, 56, 56, 256)    │        590,080 │ re_lu_12[0][0]         │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_13    │ (None, 56, 56, 256)    │          1,024 │ conv2d_13[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_13 (ReLU)           │ (None, 56, 56, 256)    │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ up_sampling2d_2           │ (None, 112, 112, 256)  │              0 │ re_lu_13[0][0]         │
│ (UpSampling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ concatenate_2             │ (None, 112, 112, 384)  │              0 │ up_sampling2d_2[0][0], │
│ (Concatenate)             │                        │                │ re_lu_3[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_14 (Conv2D)        │ (None, 112, 112, 128)  │        442,496 │ concatenate_2[0][0]    │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_14    │ (None, 112, 112, 128)  │            512 │ conv2d_14[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_14 (ReLU)           │ (None, 112, 112, 128)  │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_15 (Conv2D)        │ (None, 112, 112, 128)  │        147,584 │ re_lu_14[0][0]         │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_15    │ (None, 112, 112, 128)  │            512 │ conv2d_15[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_15 (ReLU)           │ (None, 112, 112, 128)  │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ up_sampling2d_3           │ (None, 224, 224, 128)  │              0 │ re_lu_15[0][0]         │
│ (UpSampling2D)            │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ concatenate_3             │ (None, 224, 224, 192)  │              0 │ up_sampling2d_3[0][0], │
│ (Concatenate)             │                        │                │ re_lu_1[0][0]          │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_16 (Conv2D)        │ (None, 224, 224, 64)   │        110,656 │ concatenate_3[0][0]    │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_16    │ (None, 224, 224, 64)   │            256 │ conv2d_16[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_16 (ReLU)           │ (None, 224, 224, 64)   │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_17 (Conv2D)        │ (None, 224, 224, 64)   │         36,928 │ re_lu_16[0][0]         │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ batch_normalization_17    │ (None, 224, 224, 64)   │            256 │ conv2d_17[0][0]        │
│ (BatchNormalization)      │                        │                │                        │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ re_lu_17 (ReLU)           │ (None, 224, 224, 64)   │              0 │ batch_normalization_1… │
├───────────────────────────┼────────────────────────┼────────────────┼────────────────────────┤
│ conv2d_18 (Conv2D)        │ (None, 224, 224, 1)    │             65 │ re_lu_17[0][0]         │
└───────────────────────────┴────────────────────────┴────────────────┴────────────────────────┘
 Total params: 31,401,345 (119.79 MB)
 Trainable params: 31,389,569 (119.74 MB)
 Non-trainable params: 11,776 (46.00 KB)

Loading the data for Unet Model¶

In [5]:
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn.utils import shuffle

# ✅ Function to load up to 400 binary images from a single directory
def load_binary_images(directory, img_size=(224, 224), max_images=400):
    images, masks = [], []
    file_list = sorted(os.listdir(directory))[:max_images]

    for file in file_list:
        img_path = os.path.join(directory, file)
        img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)

        if img is None:
            continue

        # Resize and normalize
        img_resized = cv2.resize(img, img_size)
        img_norm = img_resized / 255.0

        # ✅ Create binary mask where RBCs are black
        mask = (img_norm < 0.5).astype(np.uint8)

        images.append(img_norm)
        masks.append(mask)

    images = np.array(images).reshape(-1, img_size[0], img_size[1], 1)
    masks = np.array(masks).reshape(-1, img_size[0], img_size[1], 1)

    return images, masks

# ✅ Load Healthy and Anemic Datasets Separately
healthy_images, healthy_masks = load_binary_images(healthy_binary)
anemic_images, anemic_masks   = load_binary_images(anemic_binary)

# ✅ Shuffle both datasets independently
healthy_images, healthy_masks = shuffle(healthy_images, healthy_masks, random_state=42)
anemic_images, anemic_masks   = shuffle(anemic_images, anemic_masks, random_state=42)

# ✅ Print stats
print(f"✅ Loaded {len(healthy_images)} healthy images")
print(f"✅ Loaded {len(anemic_images)} anemic images")

# ✅ Show sample inputs and masks for each
def show_samples(images, masks, title=""):
    plt.figure(figsize=(10, 4))
    for i in range(5):
        plt.subplot(2, 5, i+1)
        plt.imshow(images[i].squeeze(), cmap='gray')
        plt.title(f"{title} Input")
        plt.axis("off")

        plt.subplot(2, 5, i+6)
        plt.imshow(masks[i].squeeze(), cmap='gray')
        plt.title(f"{title} Mask")
        plt.axis("off")
    plt.tight_layout()
    plt.show()

# ✅ Visual confirmation
show_samples(healthy_images, healthy_masks, title="Healthy")
show_samples(anemic_images, anemic_masks, title="Anemic")
✅ Loaded 400 healthy images
✅ Loaded 400 anemic images
No description has been provided for this image
No description has been provided for this image

Training the model¶

In [6]:
import tensorflow as tf
from sklearn.model_selection import train_test_split
import numpy as np

# ✅ Combine healthy + anemic datasets for training
train_images = np.concatenate([healthy_images, anemic_images], axis=0)
train_masks  = np.concatenate([healthy_masks, anemic_masks], axis=0)

# ✅ Split for validation
X_train, X_val, y_train, y_val = train_test_split(
    train_images, train_masks, test_size=0.2, random_state=42
)

# ✅ Enable Multi-GPU Training
strategy = tf.distribute.MirroredStrategy()
print(f"✅ Using {strategy.num_replicas_in_sync} GPUs")

# ✅ Build and compile model inside strategy scope
with strategy.scope():
    unet_model = build_unet()
    unet_model.compile(
        optimizer='adam',
        loss='binary_crossentropy',
        metrics=['accuracy', tf.keras.metrics.MeanIoU(num_classes=2)]
    )

# ✅ Train the model
history = unet_model.fit(
    X_train, y_train,
    validation_data=(X_val, y_val),
    epochs=10,
    batch_size=16,
    shuffle=True,
    verbose=1
)

# ✅ Save final model (Kaggle-compatible)
unet_model.save("/kaggle/working/unet_combined.keras")
print("✅ Model saved as: /kaggle/working/unet_combined.keras")
✅ Using 2 GPUs
Epoch 1/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 52s 672ms/step - accuracy: 0.9226 - loss: 0.1792 - mean_io_u: 0.3133 - val_accuracy: 0.3963 - val_loss: 30.0674 - val_mean_io_u: 0.2299
Epoch 2/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 25s 624ms/step - accuracy: 0.9954 - loss: 0.0320 - mean_io_u: 0.3137 - val_accuracy: 0.4370 - val_loss: 1.6180 - val_mean_io_u: 0.3039
Epoch 3/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 26s 651ms/step - accuracy: 0.9972 - loss: 0.0185 - mean_io_u: 0.3134 - val_accuracy: 0.8016 - val_loss: 0.4875 - val_mean_io_u: 0.3039
Epoch 4/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 28s 690ms/step - accuracy: 0.9976 - loss: 0.0132 - mean_io_u: 0.3158 - val_accuracy: 0.6088 - val_loss: 0.8321 - val_mean_io_u: 0.3039
Epoch 5/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 30s 742ms/step - accuracy: 0.9974 - loss: 0.0109 - mean_io_u: 0.3114 - val_accuracy: 0.6085 - val_loss: 0.9037 - val_mean_io_u: 0.3039
Epoch 6/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 29s 722ms/step - accuracy: 0.9974 - loss: 0.0093 - mean_io_u: 0.3120 - val_accuracy: 0.6441 - val_loss: 0.5477 - val_mean_io_u: 0.3039
Epoch 7/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 29s 715ms/step - accuracy: 0.9979 - loss: 0.0078 - mean_io_u: 0.3106 - val_accuracy: 0.6759 - val_loss: 0.5477 - val_mean_io_u: 0.3039
Epoch 8/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 29s 728ms/step - accuracy: 0.9980 - loss: 0.0069 - mean_io_u: 0.3147 - val_accuracy: 0.8964 - val_loss: 0.2506 - val_mean_io_u: 0.3039
Epoch 9/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 29s 723ms/step - accuracy: 0.9978 - loss: 0.0068 - mean_io_u: 0.3144 - val_accuracy: 0.9671 - val_loss: 0.0926 - val_mean_io_u: 0.3039
Epoch 10/10
40/40 ━━━━━━━━━━━━━━━━━━━━ 29s 727ms/step - accuracy: 0.9984 - loss: 0.0054 - mean_io_u: 0.3133 - val_accuracy: 0.9782 - val_loss: 0.0564 - val_mean_io_u: 0.3039
✅ Model saved as: /kaggle/working/unet_combined.keras

Predicting Mask¶

In [30]:
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

# ✅ Use Multi-GPU Strategy
strategy = tf.distribute.MirroredStrategy()
print(f"✅ Using {strategy.num_replicas_in_sync} GPUs")

# ✅ Load trained model
with strategy.scope():
    unet_model = tf.keras.models.load_model("/kaggle/working/unet_combined.keras", compile=False)

# ✅ Predict binary masks
def predict_binary_masks(model, images, threshold=0.3):
    preds = model.predict(images, batch_size=16, verbose=1)
    binary = (preds > threshold).astype(np.uint8)
    return binary

# ✅ Predict on both sets
healthy_pred_masks = predict_binary_masks(unet_model, healthy_images)
anemic_pred_masks  = predict_binary_masks(unet_model, anemic_images)

# ✅ Visualize sample predictions
def show_predictions(images, true_masks, pred_masks, title=""):
    plt.figure(figsize=(15, 6))
    for i in range(5):
        plt.subplot(3, 5, i+1)
        plt.imshow(images[i].squeeze(), cmap='gray')
        plt.title(f"{title} Input")
        plt.axis('off')

        plt.subplot(3, 5, i+6)
        plt.imshow(true_masks[i].squeeze(), cmap='gray')
        plt.title("Ground Truth")
        plt.axis('off')

        plt.subplot(3, 5, i+11)
        plt.imshow(pred_masks[i].squeeze(), cmap='gray')
        plt.title("Predicted")
        plt.axis('off')
    plt.tight_layout()
    plt.show()

# ✅ Display healthy + anemic results
show_predictions(healthy_images, healthy_masks, healthy_pred_masks, title="Healthy")
show_predictions(anemic_images, anemic_masks, anemic_pred_masks, title="Anemic")
✅ Using 2 GPUs
25/25 ━━━━━━━━━━━━━━━━━━━━ 6s 184ms/step
25/25 ━━━━━━━━━━━━━━━━━━━━ 5s 192ms/step
No description has been provided for this image
No description has been provided for this image

Contour Visualization (Watershed + Distance Transform)¶

In [36]:
import numpy as np
import cv2
import matplotlib.pyplot as plt

# ✅ Load predicted binary masks (thresholded at 0.3 already)
healthy_pred_masks = np.load("/kaggle/working/healthy_pred_masks.npy")
anemic_pred_masks  = np.load("/kaggle/working/anemic_pred_masks.npy")

def detect_rbc_contours(mask, min_area=100, max_area=3000, ar_low=0.5, ar_high=1.8):
    """Detect individual RBC contours and remove clusters based on area/aspect ratio"""
    binary = (mask.squeeze() * 255).astype(np.uint8)

    # ✅ Step 1: Morphological Closing
    kernel = np.ones((3, 3), np.uint8)
    closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)

    # ✅ Step 2: Find all contours
    contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # ✅ Step 3: Filter individual RBCs
    filtered = []
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area < min_area or area > max_area:
            continue
        x, y, w, h = cv2.boundingRect(cnt)
        aspect_ratio = w / (h + 1e-5)
        if ar_low <= aspect_ratio <= ar_high:
            filtered.append(cnt)

    return filtered, binary, closed

# ✅ Visualize and Count
def visualize_and_count(masks, title_prefix="RBCs", num=20):
    total_rbc = 0
    plt.figure(figsize=(14, num * 1.6))
    
    for i in range(num):
        contours, bin_mask, closed_mask = detect_rbc_contours(masks[i])
        total_rbc += len(contours)

        vis = cv2.cvtColor(bin_mask, cv2.COLOR_GRAY2BGR)
        cv2.drawContours(vis, contours, -1, (0, 255, 0), 1)

        plt.subplot(num, 3, i * 3 + 1)
        plt.imshow(bin_mask, cmap='gray')
        plt.title("Binary Mask")
        plt.axis("off")

        plt.subplot(num, 3, i * 3 + 2)
        plt.imshow(closed_mask, cmap='gray')
        plt.title("After Closing")
        plt.axis("off")

        plt.subplot(num, 3, i * 3 + 3)
        plt.imshow(vis)
        plt.title(f"{title_prefix} Contours → {len(contours)} RBCs")
        plt.axis("off")
    
    plt.tight_layout()
    plt.show()
    print(f"✅ {title_prefix} Total RBCs (from {num} images): {total_rbc}")
    print(f"📊 Avg RBCs per image: {total_rbc / num:.2f}")

# ✅ Visual Check with Filtering
visualize_and_count(healthy_pred_masks[:20], title_prefix="Healthy RBCs")
visualize_and_count(anemic_pred_masks[:20], title_prefix="Anemic RBCs")
No description has been provided for this image
✅ Healthy RBCs Total RBCs (from 20 images): 1362
📊 Avg RBCs per image: 68.10
No description has been provided for this image
✅ Anemic RBCs Total RBCs (from 20 images): 1087
📊 Avg RBCs per image: 54.35